home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 22 code / Paper Juggling / JuggleInterface.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-04-18  |  26.7 KB  |  1,023 lines  |  [TEXT/MMCC]

  1. /*--------------------------------------------------------------------
  2.     JuggleInterface.c
  3.     
  4.     Routines that deal with the mouse, and routines that convert
  5.     between screen coords and position in memory
  6.     
  7. ----------------------------------------------------------------------*/
  8.  
  9. #include "PaperJuggling.h"
  10. #include "DialogUtils.h"
  11.  
  12. //----------------------------------------------------
  13. // Global for double click detection
  14. static long    lastWhen = 0;
  15.  
  16. //----------------------------------------------------
  17. // mouse handling routines
  18. void DoJuggleClick(WindowPtr window, Point p, EventRecord *event)
  19. {
  20.     JuggleHandle    aJuggle = GetWindowJuggle(window);
  21.     gxPoint            clickPt;
  22.     gxHitTestInfo    result;
  23.     gxShape            shapeHit;
  24.     Boolean            doubleClick = false;
  25.     gxMapping        portMapping;
  26.     
  27.     // get mouse position in viewPort
  28.     ShortPointToFixed(&p, &clickPt);
  29.     GXGetViewPortMapping(GetWindowGXPort(window), &portMapping);
  30.     InvertMapping(&portMapping, &portMapping);
  31.     MapPoints(&portMapping, 1, &clickPt);
  32.     
  33.     // Check for double click
  34.     if ( event->when - lastWhen < GetDblTime() )
  35.     {
  36.         doubleClick = true;    // it's a double click
  37.         lastWhen = 0; // so triple clicks aren't seen as two doubles
  38.     }
  39.     else
  40.         lastWhen = event->when;
  41.  
  42.     // Look for a hit on a throw
  43.     shapeHit = GXHitTestPicture(GetDocThrowsPict(window),
  44.                                     &clickPt,
  45.                                     &result, 0, 1);
  46.     // If a hit, drag the throw
  47.     if (shapeHit != nil)
  48.     {
  49.         // Mark dirty
  50.         (*aJuggle)->dirty = true;
  51.         
  52.         // If option key is down, remove it instead
  53.         if(event->modifiers & optionKey)
  54.         {
  55.             gxLine    lineData;
  56.             
  57.             // Remove from connections data
  58.             GXGetLine(shapeHit, &lineData);
  59.             LineToRemovedThrow(aJuggle, &lineData);
  60.             
  61.             // Erase shape and remove from picture(s)
  62.             RemoveThrowShape(aJuggle, result.containerIndex);
  63.         }
  64.         else // drag it
  65.         {
  66.             // if it's not point 1 (the tail), drag point 2 (the arrow head)
  67.             // (so clicks on the line itself drag the head)
  68.             DragThrow(aJuggle, result.containerIndex, result.index, &clickPt);
  69.         }
  70.     }
  71.     // Else look for a hit on a juggler
  72.     else
  73.     {
  74.         HandPtr        hitHand;
  75.         HandLoc        aLoc = {0, 0};
  76.         gxPoint        savedPt, grid = (*aJuggle)->gridPt;
  77.         Boolean        gridded;
  78.     
  79.         // if it's close enough to a grid point
  80.         savedPt = clickPt;
  81.         gridded = Detent(&clickPt, &grid, kHandRadius);
  82.         if(gridded)
  83.         {
  84.             // get the handLoc and a pointer to the hand (don't wrap!)
  85.             PointToHandLoc(aJuggle, &clickPt, &aLoc, false);
  86.             hitHand = GetHand(aJuggle, aLoc, false);
  87.             
  88.             // if a hand or label was hit, do stuff
  89.             if(aLoc.time != 0)
  90.             {
  91.                 gxShape        hitJuggler;
  92.                 
  93.                 // Mark dirty
  94.                 (*aJuggle)->dirty = true;
  95.                 
  96.                 // get the juggler picture
  97.                 GetPictureItem((*aJuggle)->jugglersPict,
  98.                                     JugglerToRow(aJuggle, aLoc.juggler),
  99.                                     &hitJuggler, nil, nil, nil);
  100.                 
  101.                 // If command key is down, shift juggler in time
  102.                 if(event->modifiers & cmdKey)
  103.                 {
  104.                     DragJuggler(window, hitJuggler, &clickPt,
  105.                                         aLoc.juggler, hAxisOnly);
  106.                 }
  107.                 // else if the option key is down, remove juggler
  108.                 else if(event->modifiers & optionKey)
  109.                 {
  110.                     //RemoveJuggler(window, aLoc.juggler);
  111.                 }
  112.                 else if (hitHand != nil) // a hand was hit
  113.                 {
  114.                     // If the hand has no sink yet, assume the user wants
  115.                     // to add one.
  116.                     if(hitHand->sink.time == 0)
  117.                     {
  118.                         DragNewThrow(window, &clickPt);
  119.                     }
  120.                 }
  121.                 else if(aLoc.time == -1 && aLoc.juggler > 0) // label hit
  122.                 {
  123.                     // If a double click, reverse hands and redraw
  124.                     if(doubleClick)
  125.                     {
  126.                         SwitchJugglerHands(aJuggle, aLoc.juggler);
  127.                         EraseRect(&window->portRect);
  128.                         InvalRect(&window->portRect);
  129.                     }
  130.                     else
  131.                     {
  132.                         // drag juggler vertically
  133.                         DragJuggler(window, hitJuggler, &clickPt, aLoc.juggler, vAxisOnly);
  134.                     }
  135.                 }
  136.             }
  137.         }
  138.     }
  139. }
  140.  
  141. // Drag around a gray outline of the juggler, using QuickDraw
  142. void DragJuggler(WindowPtr window, gxShape jugglerPict, gxPoint *clickPt,
  143.                         short whichJuggler, short constraint)
  144. {
  145.     RgnHandle        rgn;
  146.     Point            shortGrid, qdOrigin, resultPt, qdClick;
  147.     Rect            limitRect, slopRect;
  148.     JuggleHandle    aJuggle;
  149.     gxPoint            gridPt;
  150.     gxShape            hand;
  151.     
  152.     aJuggle = GetWindowJuggle(window);
  153.     gridPt = (*aJuggle)->gridPt;
  154.             
  155.     // Set up the grid
  156.     FixedPointToShort(&gridPt, &shortGrid);
  157.  
  158.     // Set the QD origin to reflect the docPort's mapping
  159.     qdOrigin = GetQDWindowOrigin(window);
  160.     SetOrigin(qdOrigin.h, qdOrigin.v);
  161.     
  162.     // the qd mouse is converted from a viewPort mouse, so
  163.     // no need to adjust it
  164.     FixedPointToShort(clickPt, &qdClick);
  165.     
  166.     if(constraint == vAxisOnly)
  167.     {
  168.         // Get a rgn representing the bounds of the juggler
  169.         rgn = GetShapeBoundsRgn(jugglerPict);
  170.         
  171.         // Set up limit and slop rects
  172.         slopRect = window->portRect;
  173.         GetShapeBoundsQDRect(GetWindowGXShape(window), &limitRect);
  174.     }
  175.     else // assume horizontal
  176.     {
  177.         // Get a rgn representing the bounds of a hand
  178.         GetPictureItem(jugglerPict, 2, &hand, nil, nil, nil);
  179.         hand = GXCopyToShape(nil, hand);
  180.         MoveShapeCenterTo(hand, clickPt->x, clickPt->y);
  181.         rgn = GetShapeBoundsRgn(hand);
  182.         GXDisposeShape(hand);
  183.         
  184.         // Set up limit and slop rects
  185.         slopRect = window->portRect;
  186.         limitRect = (*rgn)->rgnBBox;
  187.         InsetRect(&limitRect, -shortGrid.h, 0);
  188.     }
  189.     
  190.     // drag the rgn around, gridded
  191.     resultPt = DragGrayRgnGridded(rgn, qdClick,
  192.                 &limitRect, &slopRect, constraint, shortGrid, nil);
  193.                 
  194.     // Clean up and restore origin
  195.     DisposeRgn(rgn);
  196.     SetOrigin(0, 0);
  197.     
  198.     // See what happened. If the drag ended outside the sloprect, do nothing
  199.     if(resultPt.h != 0x8000 || resultPt.v != 0x8000)
  200.     {
  201.         // Move the juggler appropriately
  202.         MoveJuggler(aJuggle, whichJuggler, resultPt.v / FixedRound(gridPt.y));
  203.         
  204.         // rebuild throws and force a redraw
  205.         RebuildThrowsPict(aJuggle);
  206.         SetPort(window);
  207.         EraseRect(&window->portRect);
  208.         InvalRect(&window->portRect);
  209.     }
  210. }
  211.  
  212. // This routine adds a new throw shape, tracks the mouse until the button is released,
  213. // then adds the new throw to the connections data
  214. void DragNewThrow(WindowPtr window, gxPoint *from)
  215. {
  216.     JuggleHandle    aJuggle;
  217.     gxLine            line;
  218.     gxPoint            jugglerGrid;
  219.     HandLoc            destLoc, sourceLoc;
  220.     
  221.     aJuggle = GetWindowJuggle(window);
  222.     jugglerGrid = (*aJuggle)->gridPt;
  223.     
  224.     // Force starting geometry to the nearest grid point
  225.     Detent(from, &jugglerGrid, jugglerGrid.x / 2);
  226.     line.first = line.last = *from;
  227.     
  228.     // Convert point to hand location
  229.     PointToHandLoc(aJuggle, &line.first, &sourceLoc, false);
  230.     if(sourceLoc.time > 0)
  231.     {
  232.         // Look for the nearest legal receiver
  233.         destLoc = FindReceiver(aJuggle, sourceLoc);
  234.         if(destLoc.time > 0)
  235.         {
  236.             // Set new receiver as the line's end.
  237.             HandLocToPoint(aJuggle, &destLoc, &line.last);
  238.             
  239.             // Make it a presentable line, off the hand a bit
  240.             line.first.x += kHandRadius;
  241.             line.last.x -= kHandRadius;
  242.                 
  243.             // Add the new throw to the throws pict, and draw it
  244.             AddNewThrowShape(aJuggle, &line, true);
  245.             
  246.             // Add to connections data to start
  247.             LineToAddedThrow(aJuggle, &line);
  248.             
  249.             // Track the drag: the arrow is index 1 in the picture, we're dragging the arrow head
  250.             DragThrow(aJuggle, 1, 2, from);
  251.         }
  252.         else
  253.             SysBeep(10); // No legal receiver
  254.     }
  255.     else
  256.         SysBeep(10); // point isn't on a hand
  257. }
  258.  
  259. // Drag the throw by the indicated point: 1 is first, 2 is last.
  260. // Assumes that the transfer mode is xor
  261. void DragThrow(JuggleHandle aJuggle, long shapeIndex,
  262.                     long whichPoint, gxPoint *from)
  263. {
  264.     gxLine        lineData;
  265.     gxShape        throwShape;
  266.     gxPoint        rawPt, goodPt, lastPt, lastGoodPt, detentGrid;
  267.     Fixed        handRadius, twoHands;
  268.     gxViewPort    port;
  269.     Boolean        legalThrow;
  270.     
  271.     // init some variables
  272.     detentGrid = (*aJuggle)->gridPt;
  273.     port = (*aJuggle)->docPort;
  274.     legalThrow = true; // The throw starts off legal by definition
  275.     handRadius = kHandRadius;
  276.     twoHands = 2 * handRadius;
  277.     
  278.     // Get current mouse, set starting positions
  279.     GXGetViewPortMouse(port, &rawPt);
  280.     lastPt = lastGoodPt = rawPt;
  281.     KeepPtInBounds(aJuggle, &lastGoodPt);
  282.     
  283.     // Get the geometry of the throw
  284.     GetPictureItem((*aJuggle)->throwsPict, shapeIndex, &throwShape, nil, nil, nil);
  285.     GXGetLine(throwShape, &lineData);
  286.     
  287.     // Remove it from the connections data while dragging
  288.     LineToRemovedThrow(aJuggle, &lineData);
  289.     
  290.     // Track the mouse, erasing and redrawing
  291.     while(StillDown())
  292.     {
  293.         // Get the new mouse position and if it's different,
  294.         // adjust the dragged shape
  295.         GXGetViewPortMouse(port, &rawPt);
  296.         if(rawPt.x != lastPt.x || rawPt.y != lastPt.y)
  297.         {
  298.             HandPtr    hand = nil;
  299.             Fixed    maxLength, minLength;
  300.             
  301.             // assume illegal throw
  302.             legalThrow = false;
  303.             
  304.             // max line length is one cycle minus 2 hands
  305.             maxLength = (detentGrid.x * ((*aJuggle)->numCounts)) - twoHands;
  306.             
  307.             // min length is one grid minus 2 hands
  308.             minLength = detentGrid.x - twoHands;
  309.  
  310.             // keep the point in bounds
  311.             goodPt = rawPt;
  312.             KeepPtInBounds(aJuggle, &goodPt);
  313.             
  314.             // dragging the tail
  315.             if(whichPoint == 1)
  316.             {
  317.                 // tail point, must be earlier in time than line's last point
  318.                 // by at least minLength
  319.                 if(goodPt.x > lineData.last.x - minLength )
  320.                     goodPt.x = lineData.last.x - minLength;
  321.                 // and line not longer than maxLength
  322.                 else if(goodPt.x < lineData.last.x - maxLength)
  323.                     goodPt.x = lineData.last.x - maxLength;
  324.                     
  325.                 // also must stay in range: throws MUST originate
  326.                 // from inside the unfaded parts
  327.                 if(goodPt.x > maxLength)
  328.                     goodPt.x = maxLength;
  329.                 
  330.                 lineData.first = goodPt; // use this point
  331.             }
  332.             else // dragging arrow head
  333.             {
  334.                 // head must be later in time than line's first point 
  335.                 // by at least minLength
  336.                 if(goodPt.x < lineData.first.x + minLength)
  337.                     goodPt.x = lineData.first.x + minLength;
  338.                 
  339.                 // but not longer than maxLength
  340.                 else if(goodPt.x > lineData.first.x + maxLength)
  341.                     goodPt.x = lineData.first.x + maxLength;
  342.                 
  343.                 lineData.last = goodPt; // use this point
  344.             }
  345.             
  346.             // Check if it's near a grid point
  347.             if(Detent(&goodPt, &detentGrid, kThrowDetent))
  348.             {
  349.                 // It is, get the hand
  350.                 hand = PointToHand(aJuggle, &goodPt, true);
  351.                 if(hand != 0)
  352.                 {
  353.                     if(whichPoint == 1) // if dragging tail
  354.                     {
  355.                         if(hand->sink.time == 0) // and hand needs a sink
  356.                         {
  357.                             lineData.first = goodPt; // Legal change, so change it
  358.                             lineData.first.x += handRadius;  // get it off the hand
  359.                             legalThrow = true;
  360.                         }
  361.                         else
  362.                         {
  363.                             // !!! repel?
  364.                             // Hand doesn't need a sink, restore goodPt to pre-Detent value
  365.                             goodPt = lineData.first;
  366.                         }
  367.                     }
  368.                     else // dragging arrow head
  369.                     {
  370.                         if(hand->source.time == 0) // if hand needs a source
  371.                         {
  372.                             lineData.last = goodPt; // Legal change, so change it
  373.                             lineData.last.x -= handRadius;  // get it off the hand
  374.                             legalThrow = true;
  375.                         }
  376.                         else
  377.                         {
  378.                             // !!! repel?
  379.                             // Hand doesn't need a source, restore goodPt to pre-Detent value
  380.                             goodPt = lineData.last;
  381.                         }
  382.                     }
  383.                 }
  384.             }
  385.               
  386.               // save last point
  387.             lastPt = rawPt;
  388.             
  389.             // Only redraw if the new point is different than the last
  390.             if(goodPt.x != lastGoodPt.x || goodPt.y != lastGoodPt.y)
  391.             {
  392.                 // Erase old and draw new
  393.                 MoveThrowShape(aJuggle, shapeIndex, &lineData);
  394.                 
  395.                 // Save this good point
  396.                 lastGoodPt = goodPt;
  397.             }
  398.  
  399.         } // if mouse moved
  400.     } // while still down
  401.     
  402.     // Mouse button released. If a legal throw, add it back in to connections data
  403.     if(legalThrow)
  404.         LineToAddedThrow(aJuggle, &lineData);
  405.     else // if illegal, erase and remove from the pictures
  406.     {
  407.         RemoveThrowShape(aJuggle, shapeIndex);
  408.     }
  409. }
  410.  
  411. // Like DragGrayRgn, but the region is constrained to grid points
  412. Point DragGrayRgnGridded(RgnHandle theRgn, Point startPt, Rect *limitRect,
  413.                         Rect *slopRect, short axis, Point grid, Point *initialOffset)
  414. {
  415.     Point        result, goodPt, rawMouse, lastGoodPt, lastRawMouse,
  416.                 gridOffset, startGridOffset;
  417.     Boolean     outsideSlop;
  418.     PenState     savedPen;
  419.     
  420.     // Initialize variables
  421.     if(initialOffset != nil)
  422.         AddPt(*initialOffset, &startPt);
  423.         
  424.     goodPt = lastGoodPt = rawMouse = lastRawMouse = startPt; // All the same to start
  425.     result.h = result.v = 0;
  426.     outsideSlop = false;
  427.     
  428.     // Save the pen state, set mode to xor
  429.     GetPenState(&savedPen);
  430.     PenNormal();
  431.     PenMode(srcXor);
  432.     PenPat(&qd.gray);
  433.     
  434.     // Draw the rgn to start
  435.     FrameRgn(theRgn);
  436.     
  437.     // We are gridding the rgn, not the point itself, so we need to remember
  438.     // the amount the original point was off the grid.
  439.     startGridOffset = startPt;
  440.     GetShortGridOffset(&startGridOffset, grid);
  441.  
  442.     // Track the mouse
  443.     while(StillDown())
  444.     {
  445.         // Get the current mouse point.
  446.         GetMouse(&rawMouse);
  447.         
  448.         if(initialOffset != nil)
  449.             AddPt(*initialOffset, &rawMouse);
  450.         
  451.         // If the mouse moved, do stuff . . .
  452.         if(rawMouse.h != lastRawMouse.h || rawMouse.v != lastRawMouse.v)
  453.         {
  454.             // if it's inside the slopRect...
  455.             if(PtInRect(rawMouse, slopRect))
  456.             {
  457.                 // if it's been outside the slopRect but is back inside…
  458.                 if(outsideSlop)
  459.                 {
  460.                     // …redraw it, and forget it was outside
  461.                     FrameRgn(theRgn);
  462.                     outsideSlop = false;
  463.                 }
  464.                 
  465.                 // We'll calculate a "good" (legal) point from the raw mouse
  466.                 goodPt = rawMouse;
  467.                 
  468.                 // if it's outside the limitRect but inside the slopRect...
  469.                 if(!PtInRect(goodPt, limitRect))
  470.                 {
  471.                     long    temp;
  472.                     
  473.                     // ...pin the point to the limitRect
  474.                     temp = PinRect(limitRect, goodPt);
  475.                     goodPt = *(Point *)(&temp);
  476.                 }
  477.  
  478.                 // Constrain the point to the grid, then add back the original offset,
  479.                 // so we keep the same offset from the grid points
  480.                 gridOffset = goodPt;
  481.                 GetShortGridOffset(&gridOffset, grid);
  482.                 goodPt.h = goodPt.h - gridOffset.h + startGridOffset.h;
  483.                 goodPt.v = goodPt.v - gridOffset.v + startGridOffset.v;
  484.                 
  485.                 // if it's axis constrained, constrain it
  486.                 if(axis != noConstraint)
  487.                 {
  488.                     if(axis == hAxisOnly)
  489.                         goodPt.v = lastGoodPt.v; // keep the vertical the same
  490.                     else // assume vAxisOnly
  491.                         goodPt.h = lastGoodPt.h; // keep the horizontal the same
  492.                 }
  493.                 
  494.                 // OK, we have a good point. if it's different than the last,
  495.                 // move the rgn
  496.                 if(goodPt.h != lastGoodPt.h || goodPt.v != lastGoodPt.v)
  497.                 {
  498.                     // Erase it, Move it, redraw it
  499.                     FrameRgn(theRgn);
  500.                     OffsetRgn(theRgn, goodPt.h - lastGoodPt.h, goodPt.v - lastGoodPt.v);
  501.                     FrameRgn(theRgn);
  502.                 }
  503.                 // remember the good point
  504.                 lastGoodPt = goodPt;
  505.             }
  506.             else // it's outside the slopRect
  507.             {
  508.                 // if it just went outside...
  509.                 if(!outsideSlop)
  510.                 {
  511.                     // ...erase the rgn, and remember the point is outside
  512.                     FrameRgn(theRgn);
  513.                     outsideSlop = true;
  514.                 }
  515.             }
  516.             // remember the mouse position
  517.             lastRawMouse = rawMouse;
  518.         }
  519.     }
  520.     
  521.     // If the final point is still outside the slopRect, return "nothing"
  522.     if(outsideSlop)
  523.         result.h = result.v = 0x8000;
  524.     // Otherwise use the offset from the start to the existing good point,
  525.     // and don't forget to erase the Rgn for the last time
  526.     else
  527.     {
  528.         result.h = lastGoodPt.h - startPt.h;
  529.         result.v = lastGoodPt.v - startPt.v;
  530.         FrameRgn(theRgn);
  531.     }
  532.  
  533.     // Restore pen
  534.     SetPenState(&savedPen);
  535.     
  536.     return result;
  537. }
  538.  
  539.  
  540. void KeepPtInBounds(JuggleHandle aJuggle, gxPoint *point)
  541. {
  542.     gxPoint    grid = (*aJuggle)->gridPt;
  543.     
  544.     // Keep it from falling off the left edge
  545.     if(point->x < 0)
  546.         point->x = 0;
  547.     
  548.     // or the right edge (TWICE the juggle unit)
  549.     else if(point->x > grid.x * ((*aJuggle)->numCounts * 2 - 1))
  550.         point->x = grid.x * ((*aJuggle)->numCounts * 2 - 1);
  551.     
  552.     // and not off the bottom or top
  553.     if(point->y < 0)
  554.         point->y = 0;
  555.     else if(point->y > grid.y * ((*aJuggle)->numJugglers - 1))
  556.         point->y = grid.y * ((*aJuggle)->numJugglers - 1);
  557. }
  558.  
  559. //----------------------------------------------------
  560. // grid enforcement routines
  561.  
  562. // This routine returns the amount the given point is off the
  563. // given grid.
  564. void GetGridOffset(gxPoint *pointData, gxPoint *grid)
  565. {
  566.     gxPoint    localPt = *pointData;
  567.     
  568.     // Calculate the amount off grid (remainder of dividing by the grid)
  569.     localPt.x %= grid->x;
  570.     localPt.y %= grid->y;
  571.     
  572.     // If the amount off grid is greater than half the grid spacing,
  573.     // (or less than its negative) then the point is 
  574.     // "before" the nearest grid point. Make it a small opposite signed
  575.     //  offset instead of a large one, so we can just subtract it from the
  576.     // point to grid it.
  577.     if(localPt.x < 0)
  578.     {
  579.         if(localPt.x < -(grid->x / 2))
  580.             localPt.x = grid->x + localPt.x;
  581.     }
  582.     else
  583.     {
  584.         if(localPt.x > (grid->x / 2))
  585.             localPt.x = localPt.x - grid->x;
  586.     }
  587.     if(localPt.y < 0)
  588.     {
  589.         if(localPt.y < -(grid->y / 2))
  590.             localPt.y = grid->y + localPt.y;
  591.     }
  592.     else
  593.     {
  594.         if(localPt.y > (grid->y / 2))
  595.             localPt.y = localPt.y - grid->y;
  596.     }
  597.     
  598.     *pointData = localPt;
  599. }
  600.  
  601. // This routine returns the amount the given point is off the
  602. // given grid. (integer coords version)
  603. void GetShortGridOffset(Point *pointData, Point grid)
  604. {
  605.     Point    localPt = *pointData;
  606.     
  607.     // Calculate the amount off grid (remainder of dividing by the grid)
  608.     localPt.h %= grid.h;
  609.     localPt.v %= grid.v;
  610.     
  611.     // If the amount off grid is greater than half the grid spacing,
  612.     // (or less than its negative) then the point is 
  613.     // "before" the nearest grid point. Make it a small opposite signed
  614.     //  offset instead of a large one, so we can just subtract it from the
  615.     // point to grid it.
  616.     if(localPt.h < 0)
  617.     {
  618.         if(localPt.h < -(grid.h / 2))
  619.             localPt.h = grid.h + localPt.h;
  620.     }
  621.     else
  622.     {
  623.         if(localPt.h > (grid.h / 2))
  624.             localPt.h = localPt.h - grid.h;
  625.     }
  626.     if(localPt.v < 0)
  627.     {
  628.         if(localPt.v < -(grid.v / 2))
  629.             localPt.v = grid.v + localPt.v;
  630.     }
  631.     else
  632.     {
  633.         if(localPt.v > (grid.v / 2))
  634.             localPt.v = localPt.v - grid.v;
  635.     }
  636.     
  637.     *pointData = localPt;
  638. }
  639.  
  640. // This routine checks the given point to see if it's
  641. // within detentLimit of a grid point. If it is, the
  642. // point is set to the grid point, and true is returned.
  643. // Otherwise it's unchanged and false is returned.
  644. Boolean Detent(gxPoint *pointData, gxPoint *grid, Fixed detentLimit)
  645. {
  646.     gxPoint        localPt = *pointData;
  647.     Boolean        result;
  648.     
  649.     // Calculate the amount off grid
  650.     GetGridOffset(&localPt, grid);
  651.     
  652.     // Check for a touch. If so, grid the point and return true.
  653.     if(fixAbs(localPt.x) < detentLimit && fixAbs(localPt.y) < detentLimit)
  654.     {
  655.         pointData->x -= localPt.x;
  656.         pointData->y -= localPt.y;
  657.         result = true;
  658.     }
  659.     else
  660.         result = false;
  661.     
  662.     return result;
  663. }
  664.  
  665. // This routine forces the given point to the nearest grid position,
  666. // then converts it to a 1-based grid location instead of a location in 
  667. // the view port's coordinates.
  668. void SetGridPos(gxPoint *pointData, gxPoint *grid)
  669. {
  670.     gxPoint    localPt = *pointData;
  671.     
  672.     // Calculate the amount off grid
  673.     GetGridOffset(&localPt, grid);
  674.     
  675.     // Force point to nearest grid point
  676.     pointData->x -= localPt.x;
  677.     pointData->y -= localPt.y;
  678.     
  679.     // Convert to a 1-based grid location, fixed
  680.     pointData->x /= grid->x;             // integer result
  681.     pointData->x = ff(pointData->x);     // convert back to fixed
  682.     pointData->y /= grid->y;             // integer result
  683.     pointData->y = ff(pointData->y);     // convert back to fixed
  684. }
  685.  
  686. //----------------------------------------------------
  687. // Screen to memory conversion routines
  688.  
  689. // change the jugglerMap to reflect a moved juggler
  690. void MoveJugglerInMap(JuggleHandle aJuggle, short whichJuggler,
  691.                         short fromRow, short toRow)
  692. {
  693.     short rowDistance, increment, index;
  694.     
  695.     // Sanity Check
  696.      if(whichJuggler != RowToJuggler(aJuggle, fromRow))
  697.     {
  698.         SysBeep(10);
  699.         return;
  700.     }
  701.  
  702.     // see how far we moved
  703.     rowDistance = toRow - fromRow;
  704.     
  705.     index = fromRow - 1;
  706.     if(rowDistance < 0)
  707.     {
  708.         // moving up
  709.         increment = -1;
  710.         rowDistance = -rowDistance;
  711.     }
  712.     else
  713.     {
  714.         // moving down
  715.         increment = 1;
  716.     }
  717.     
  718.     // Move the block of jugglers between the from and to rows
  719.     while(rowDistance-- > 0)
  720.     {
  721.         (*aJuggle)->jugglerMap[index] = (*aJuggle)->jugglerMap[index + increment];
  722.         index += increment;
  723.     }
  724.     
  725.     // insert moved juggler in destination row
  726.     (*aJuggle)->jugglerMap[toRow - 1] = whichJuggler;
  727. }        
  728.  
  729. // Convert a juggler number to a row in the onscreen picture
  730. short JugglerToRow(JuggleHandle aJuggle, short juggler)
  731. {
  732.     short count;
  733.     
  734.     // Brute force and awkwardness: Go through the map looking for this juggler
  735.     for(count = 0; count < (*aJuggle)->numJugglers; count++)
  736.     {
  737.         if((*aJuggle)->jugglerMap[count] == juggler)
  738.         {
  739.             // Found it
  740.             return (count + 1);
  741.         }
  742.     }
  743.     return 0;
  744. }
  745.  
  746. // Convert a row in the onscreen picture to a juggler number
  747. short RowToJuggler(JuggleHandle aJuggle, short row)
  748. {
  749.     // range check first
  750.     if(row < 1 || row > (*aJuggle)->numJugglers)
  751.         return 0;
  752.     return (*aJuggle)->jugglerMap[row - 1];
  753. }
  754.  
  755. // Convert a HandLoc to a point in the view port
  756. void HandLocToPoint(JuggleHandle aJuggle, HandLoc *hand, gxPoint *point)
  757. {
  758.     gxPoint        grid = (*aJuggle)->gridPt;
  759.     
  760.     // The x coord is simply (time - 1) * xgrid.
  761.     point->x = (hand->time - 1) * grid.x;
  762.     
  763.     // Get the point by first getting the right row, then multiplying by
  764.     // the grid spacing.
  765.     point->y = (JugglerToRow(aJuggle, hand->juggler) - 1) * grid.y;
  766. }
  767.  
  768. // Convert a point in the viewPort to a HandLoc (no rounding, the point is 
  769. // assumed to be gridded already)
  770. void PointToHandLoc(JuggleHandle aJuggle, gxPoint *point, HandLoc *hand,
  771.                         Boolean wrapIt)
  772. {
  773.     gxPoint        grid = (*aJuggle)->gridPt;
  774.     
  775.     // The time is simply the point's x coord divided by the grid.
  776.     // (adjusted up for a 1-based time location)
  777.     // Divide a Fixed by a Fixed and you get an integer result
  778.     hand->time = (point->x / grid.x) + 1;
  779.     // range check
  780.     if(hand->time == 0) // label hit
  781.         hand->time = -1;    // signal to caller that label was hit
  782.     // if off the right edge
  783.     else if(hand->time > (*aJuggle)->numCounts)
  784.     {
  785.         // if wrapping, make it a legal time, else out of range, make it 0
  786.         if(wrapIt)
  787.         {
  788.             hand->time %= (*aJuggle)->numCounts; // keep in range
  789.             // !!! if it's zero, we've hit a special case: the last faded
  790.             // juggler on the right. It should really wrap to the last solid
  791.             // juggler on the right.
  792.             if(hand->time == 0)
  793.                 hand->time = (*aJuggle)->numCounts;
  794.         }
  795.         else
  796.             hand->time = 0;
  797.     }
  798.     else if(hand->time < 0)
  799.         hand->time = 0;
  800.     
  801.     // Get the juggler by first dividing to get the (1-based) row, then converting
  802.     hand->juggler = RowToJuggler(aJuggle, (point->y / grid.y) + 1);
  803. }
  804.  
  805. // Given the point on screen, return a pointer to the specific hand
  806. // (no rounding, the point is assumed to be "gridded" already)
  807. HandPtr PointToHand(JuggleHandle aJuggle, gxPoint *point, Boolean wrapIt)
  808. {
  809.     HandLoc    aLoc;
  810.     
  811.     PointToHandLoc(aJuggle, point, &aLoc, wrapIt);
  812.     return GetHand(aJuggle, aLoc, wrapIt);
  813. }
  814.  
  815. // Convenience routine that takes a gxLine instead of two HandLocs
  816. void LineToAddedThrow(JuggleHandle aJuggle, gxLine *line)
  817. {
  818.     HandLoc    from, to;
  819.     gxLine    gridLine;
  820.     
  821.     // Line doesn't really reach grid points, so grid it
  822.     gridLine = *line;
  823.     gridLine.first.x -= kHandRadius;
  824.     gridLine.last.x += kHandRadius;
  825.     
  826.     // convert points to HandLocs, wrapping them around
  827.     PointToHandLoc(aJuggle, &gridLine.first, &from, true);
  828.     PointToHandLoc(aJuggle, &gridLine.last, &to, true);
  829.  
  830.     AddThrow(aJuggle, from, to);
  831. }
  832.  
  833. // Convenience routine that takes a gxLine instead of two HandLocs
  834. void LineToRemovedThrow(JuggleHandle aJuggle, gxLine *line)
  835. {
  836.     HandLoc    from, to;
  837.     gxLine    gridLine;
  838.     
  839.     // Line doesn't really reach grid points, so grid it
  840.     gridLine = *line;
  841.     gridLine.first.x -= kHandRadius;
  842.     gridLine.last.x += kHandRadius;
  843.     
  844.     // convert points to HandLocs, wrapping them around
  845.     PointToHandLoc(aJuggle, &gridLine.first, &from, true);
  846.     PointToHandLoc(aJuggle, &gridLine.last, &to, true);
  847.  
  848.     RemoveThrow(aJuggle, from, to);
  849. }
  850.  
  851. //----------------------------------------------------
  852. // Dialog routines
  853.  
  854. // Ask the user how big to make the juggle, return zero if they cancel
  855. void DoJuggleSizeDialog(short *numJugglers, short *numCounts)
  856. {
  857.     DialogPtr            theDlog;
  858.     short                hit, kind;
  859.     Handle                itmhndl;
  860.     Rect                rect;
  861.     GrafPtr                oldport;
  862.     UserItemUPP            outlineUPP;
  863.     ModalFilterUPP        numFilterUPP;
  864.     Boolean             exitDialog = false;
  865.     short                startJugglers = kDefaultJugglers, startCounts = kDefaultCounts;
  866.     
  867.     WindowPtr            frontWind;
  868.     JuggleHandle        aJuggle;
  869.         
  870.     
  871.     // Assume failure
  872.     *numJugglers = *numCounts = 0;
  873.     
  874.     /* Save the current port */
  875.     GetPort(&oldport);
  876.     
  877.     // Get the size of the front juggle, if there is one.
  878.     frontWind = FrontWindow();
  879.     aJuggle = GetWindowJuggle(frontWind);
  880.     if(aJuggle != nil)
  881.     {
  882.         startJugglers = (*aJuggle)->numJugglers;
  883.         startCounts = (*aJuggle)->numCounts;
  884.     }
  885.     
  886.     /* Get the dialog (invisible) */
  887.     theDlog = GetNewDialog(kJuggleSizeDialogId, nil, (WindowPtr)(-1));
  888.     if(theDlog == nil)
  889.         return;
  890.     
  891.     // Set up UPPs
  892.     outlineUPP = NewUserItemProc(BtnItem);
  893.     numFilterUPP = NewModalFilterProc(NumFilter);
  894.     
  895.     /* Install OK button Outline */
  896.     GetDialogItem(theDlog, iOKOutline, &kind, &itmhndl, &rect);
  897.     SetDialogItem(theDlog, iOKOutline, userItem, (Handle)outlineUPP, &rect);
  898.     
  899.     /* Other user Items... */
  900.     
  901.     /* Set Values of text items... */
  902.     ShortToDlog(startJugglers, theDlog, iNumJugglers);
  903.     ShortToDlog(startCounts, theDlog, iNumCounts);
  904.         
  905.     /* Do it... */
  906.     SelectDialogItemText(theDlog, iNumJugglers, 0, 32767);
  907.     ShowWindow(theDlog);
  908.     while(!exitDialog)                                    /* Loop until break...*/
  909.     {
  910.         ModalDialog(numFilterUPP, &hit); /* NumFilter only allows numeric input */
  911.         switch(hit)
  912.         {
  913.             case iOKButton:                /* OK button hit...*/
  914.                 if(JuggleSizeOK(theDlog))
  915.                 {
  916.                     *numJugglers = DlogToShort(theDlog, iNumJugglers);
  917.                     *numCounts = DlogToShort(theDlog, iNumCounts);
  918.                     // Make sure numCounts is even
  919.                     if((*numCounts % 2) == 1)
  920.                         *numCounts += 1;
  921.                     exitDialog = true;
  922.                 }
  923.                 break;
  924.     
  925.             case iCancelButton:            /* Cancel button hit...*/
  926.                 exitDialog = true;
  927.                 break;
  928.     
  929.             default:                    /* Anything else hit... */
  930.                 continue;
  931.         }
  932.     }
  933.     SetCursor(&qd.arrow);
  934.  
  935.     // Dispose UPPs
  936.     DisposeRoutineDescriptor(outlineUPP);
  937.     DisposeRoutineDescriptor(numFilterUPP);
  938.     
  939.     DisposDialog(theDlog);
  940. }
  941.  
  942. // This routine checks the size of the juggle, if it's not OK it beeps, selects the text
  943. // that needs fixing, and returns false.
  944. Boolean    JuggleSizeOK(DialogPtr dptr)
  945. {
  946.     short    type, theNum;
  947.     Rect    rect;
  948.     Handle    hndl;
  949.     Boolean rslt = false;
  950.     
  951.     // Check num jugglers
  952.     GetDItem(dptr, iNumJugglers, &type, &hndl, &rect);
  953.     theNum = DlogToShort(dptr, iNumJugglers);
  954.     if(theNum < 1 || theNum > kMaxJugglers)
  955.     {
  956.         SysBeep(10);
  957.         SelectDialogItemText(dptr, iNumJugglers, 0, 32767);
  958.         return false;
  959.     }
  960.     
  961.     // Check num counts
  962.     GetDItem(dptr, iNumCounts, &type, &hndl, &rect);
  963.     theNum = DlogToShort(dptr, iNumJugglers);
  964.     if(theNum < 1 || theNum > kMaxCounts)
  965.     {
  966.         SysBeep(10);
  967.         SelectDialogItemText(dptr, iNumCounts, 0, 32767);
  968.         return false;
  969.     }
  970.     return true;
  971. }
  972.     
  973. //----------------------------------------------------
  974. // String utility routines, from DTS
  975.  
  976.  
  977. /* Return the length of the c-string.  (Same as strlen, but this function isn't
  978. ** part of the string library.  The entire library may be more than you wish to
  979. ** link into the code segment that holds main, so this (and other) standard
  980. ** library function has been duplicated here. */
  981. short    clen(char *cptr)
  982. {
  983.     short    i;
  984.  
  985.     for (i = 0; cptr[i]; ++i) {};
  986.     return(i);
  987. }
  988.  
  989. /* Catenate two c-strings. */
  990. char    *ccat(char *s1, char *s2)
  991. {
  992.     ccpy(s1 + clen(s1), s2);
  993.     return(s1);
  994. }
  995.  
  996. /* Copy a c-string. */
  997. char    *ccpy(char *s1, char *s2)
  998. {
  999.     char    *c1, *c2;
  1000.  
  1001.     c1 = s1;
  1002.     c2 = s2;
  1003.     while ((*c1++ = *c2++) != 0) {};
  1004.     return(s1);
  1005. }
  1006.  
  1007. /* Convert a pascal-string to a c-string. */
  1008. void    p2c(StringPtr cptr)
  1009. {
  1010.     unsigned char    len;
  1011.  
  1012.     BlockMove(cptr + 1, cptr, len = *cptr);
  1013.     cptr[len] = 0;
  1014. }
  1015.  
  1016. short min(short a, short b)
  1017. {
  1018.     if(b < a)
  1019.         return b;
  1020.     else
  1021.         return a;
  1022. }
  1023.